use std::collections::HashMap;

use model_graph_types::{
    connector::{Connector, ExecuteOptions, QueryOptions},
    database::{ConnectorProtocol, DataSource, DatabaseType},
    modeling::Value,
};

use dgraph_connector::DgraphConnector;
use jdbc_connector::JdbcConnector;
use mysql_connector::MySqlConnector;
use sqlite_connector::SqliteConnector;

pub struct UnionConnector {
    data_source: DataSource,
    connector: anyhow::Result<Box<dyn Connector + std::marker::Sync + Send>>,
}

impl UnionConnector {
    pub async fn open(data_source: &DataSource) -> anyhow::Result<UnionConnector> {
        match data_source.protocol {
            ConnectorProtocol::MySQL => {
                //
                let connector = UnionConnector {
                    data_source: data_source.clone(),
                    connector: Ok(Box::new(MySqlConnector::open(data_source).await?)),
                };
                Ok(connector)
            }
            ConnectorProtocol::SQLite => {
                let connector = UnionConnector {
                    data_source: data_source.clone(),
                    connector: Ok(Box::new(SqliteConnector::open(data_source).await?)),
                };
                Ok(connector)
            }
            ConnectorProtocol::Jdbc => {
                let connector = UnionConnector {
                    data_source: data_source.clone(),
                    connector: Ok(Box::new(JdbcConnector::open(data_source).await?)),
                };
                Ok(connector)
            }
            ConnectorProtocol::Dgraph => {
                let connector = UnionConnector {
                    data_source: data_source.clone(),
                    connector: Ok(Box::new(DgraphConnector::open(data_source).await?)),
                };
                Ok(connector)
            }
        }
    }
}

#[async_trait::async_trait]
impl Connector for UnionConnector {
    async fn query(
        &self,
        sql: &String,
        args: &Vec<Value>,
        options: QueryOptions,
    ) -> anyhow::Result<(Vec<Value>, Vec<Value>)> {
        self.connector
            .as_ref()
            .map_err(|err| anyhow::anyhow!("{}", err))?
            .query(sql, args, options)
            .await
    }

    async fn query_one(
        &self,
        sql: &String,
        args: &Vec<Value>,
        options: QueryOptions,
    ) -> anyhow::Result<(Vec<Value>, Value)> {
        self.connector
            .as_ref()
            .map_err(|err| anyhow::anyhow!("{}", err))?
            .query_one(sql, args, options)
            .await
    }

    async fn query_map(
        &self,
        sql: &String,
        args: &Vec<Value>,
        options: QueryOptions,
    ) -> anyhow::Result<HashMap<String, Value>> {
        self.connector
            .as_ref()
            .map_err(|err| anyhow::anyhow!("{}", err))?
            .query_map(sql, args, options)
            .await
    }

    async fn query_maps(
        &self,
        sql: &String,
        args: &Vec<Value>,
        options: QueryOptions,
    ) -> anyhow::Result<Vec<HashMap<String, Value>>> {
        self.connector
            .as_ref()
            .map_err(|err| anyhow::anyhow!("{}", err))?
            .query_maps(sql, args, options)
            .await
    }

    async fn execute(
        &self,
        sql: &String,
        args: &Vec<Value>,
        options: ExecuteOptions,
    ) -> anyhow::Result<i64> {
        //
        self.connector
            .as_ref()
            .map_err(|err| anyhow::anyhow!("{}", err))?
            .execute(sql, args, options)
            .await
    }

    async fn close(&mut self) -> anyhow::Result<i32> {
        self.connector
            .as_mut()
            .map_err(|err| anyhow::anyhow!("{}", err))?
            .close()
            .await
    }
}
